/*
 *  Map.cpp
 *  Rempart
 *
 *
 */

#include "Map.h"

Map* Map::instance(0);

Map * Map::getInstance() {
	
	if (instance == 0) {
		instance = new Map();
	}
	return instance;
}

Map::Map(){
	printf("Creation de la map  %d x %d\n",Map::width, Map::heigth);
	this->SetPosition(0,Menu::heigth);
	this->mouseOverElements = new Index<MapElement*>;
	this->canonList = new Index<Canon*>;
	this->wallList = new Index<Wall*>;
	this->towerList= new Index<Tower*>; 
	this->boatList = new Index<Boat*>;
	this->waterList = new Index<Water*>;
	this->securedList = new Index<MapElement*>;
		
	this->loadFromFile();
}

Map::~Map(){
	
		
	for (int i = 0; this->isInMap(i, 0); i++) {
		for (int u = 0; this->isInMap(i, u); u++) {
			for (int v = 0; v < Map::layers; v++) {
				delete this->mapElement[v][i][u];
			}
		}		
	}
	
	// Le destructeur de Index effectue lui même le vidage des cases avec clear().
	delete this->canonList;
	delete this->securedList;
	delete this->wallList;
	delete this->towerList;
	delete this->boatList;
	delete this->waterList;
	delete this->mouseOverElements;
		


	
	printf("Map supprimée.\n");
}


/*
 * MOUSEOVER FUNCTIONS
 */

void Map::setMouseOver(MouseOverPattern* pattern,int x,int y){
	
	sf::Lock Lock(globalLock);
	this->unsetMouseOver();
	
	std::vector <sf::Vector2<int>* > coordonnees;
	for (int i = 0; i < 3; i++) {
		for (int u = 0; u < 3; u++) {
			if(pattern->isSelected(i,u)){
				coordonnees.push_back(new sf::Vector2<int>(x + (i - 1),y + (u - 1)));
			}
		}
	}
	int error =0;
	for (int i = 0; i < coordonnees.size(); i++) {
		if (!this->isInMap(coordonnees.at(i)->x,coordonnees.at(i)->y)){
			error++;
		}
	}
	if(error == 0){
		for (int i = 0; i < coordonnees.size(); i++) {
			this->setMouseOver(coordonnees.at(i)->x,coordonnees.at(i)->y);
		}
	}
	
}


void Map::setMouseOver(int x,int y){

	if(isInMap(x, y)){
		if (mapElement[1][x][y]->getElementType() != MapElement::empty || mapElement[0][x][y]->getElementType() != MapElement::ground ) {
			this->mapElement[2][x][y]->setMouseOver(true);
		}
		else {
			this->mapElement[2][x][y]->setMouseOver();
		}

		this->mouseOverElements->addObject(this->mapElement[2][x][y]);
	}
}

void Map::unsetMouseOver(int x, int y){

	this->mapElement[2][x][y]->unsetMouseOver();
}

void Map::unsetMouseOver(){
	
	if(this->mouseOverElements->size() > 0){
		for (int i = this->mouseOverElements->size()-1; i >=0; i--) {
			sf::Vector2<int> casePos= this->mouseOverElements->getObjectMapPositionAt(i);
			unsetMouseOver(casePos.x,casePos.y);
			this->mouseOverElements->removeObjectAt(i);
		}
	}
}


/*
 * WALL FUNCTIONS
 */


void Map::buildWall(int x, int y){
	Wall* myWall = new Wall();
	delete this->mapElement[1][x][y];
	this->mapElement[1][x][y] = myWall;
	this->mapElement[1][x][y]->SetPosition(x*MapElement::getWidth(), y*MapElement::getHeigth());
	this->wallList->addMapElement(this->mapElement[1][x][y]);
}

void Map::buildWall(){
	sf::Lock Lock(globalLock);
	int error = 0;
	if(this->mouseOverElements->size() < 1 ){
		throw CollisionException();
	}
	for (int i = 0; i < this->mouseOverElements->size(); i++) {
		
		int caseX = this->mouseOverElements->getObjectMapPositionAt(i).x;
		int caseY = this->mouseOverElements->getObjectMapPositionAt(i).y;
		
		if (!this->isInMap(caseX,caseY)){
			error++;
		}
		else if (this->mapElement[0][caseX][caseY]->getElementType() != MapElement::ground){
			error++;
		}
		else if (this->mapElement[1][caseX][caseY] == NULL ){
			error++;
		}
		else if(this->mapElement[1][caseX][caseY]->getElementType() != MapElement::empty) {
			error++;
		}
	}
			
	if (error == 0) {
		for (int i = 0; i < this->mouseOverElements->size(); i++) {
			int caseX = this->mouseOverElements->getObjectMapPositionAt(i).x;
			int caseY = this->mouseOverElements->getObjectMapPositionAt(i).y;
			this->buildWall(caseX,caseY);
		}
		
		for (int i = 0; i < this->mouseOverElements->size(); i++) {
			int caseX = this->mouseOverElements->getObjectMapPositionAt(i).x;
			int caseY = this->mouseOverElements->getObjectMapPositionAt(i).y;
			this->checkZone(caseX,caseY);
		}
		
	}
	else {
		throw CollisionException();
	}

}	

void Map::destroyWall(int x, int y){
	if (isInMap(x, y)) {
		if(this->mapElement[1][x][y]->getElementType() == MapElement::wall) {
			int id = this->mapElement[1][x][y]->getId();
			
			MapElement* myElem = new MapElement;
			myElem->SetPosition(this->wallList->getObjectPositionAt(id));
			this->wallList->deleteObjectAt(id);
			this->mapElement[1][x][y] = myElem;
			this->mapElement[1][x][y]->SetPosition(x*MapElement::getWidth(), y*MapElement::getHeigth());
		}
	}
}


/*
 * CANON FUNCTIONS
 */

void Map::buildCanon(){
	this->buildCanon(EventManager::getInstance()->getMouseCasePosition().x,EventManager::getInstance()->getMouseCasePosition().y);
}
void Map::buildCanon(int x, int y){
	if(this->isInMap(x,y) && this->mapElement[0][x][y]->getElementType() == MapElement::ground && this->mapElement[0][x][y]->isSecured() && this->mapElement[1][x][y]->getElementType() == MapElement::empty){
		securedList->deleteObjectAt(this->mapElement[1][x][y]->getId());
		Canon* myCanon = new Canon();
		myCanon->SetPosition(x*MapElement::getWidth(),y*MapElement::getHeigth());
		this->mapElement[1][x][y] = myCanon;
		this->securedList->addObject(this->mapElement[1][x][y]);
		this->canonList->addObject(myCanon);
	}
	else {
		throw CollisionException();
	}

}


bool Map::isInMap(int x, int y){
	int error = 0;
	if(x < 0){
		error++;
	}
	if ( x > Map::width/MapElement::getHeigth()-1) {
		error++;
	}
	if ( y < 0){
		error++;
	}
	if (y > Map::heigth/MapElement::getHeigth()-1){
		error++;
	}
	
	if (error == 0) {
		return true;
	}
	else {
		return false;
	}
	
}

bool Map::isInMap(const sf::Vector2<float>& position){
	return this->isInMap((int) position.x/MapElement::getWidth(),(int) position.y/MapElement::getHeigth());
}

bool Map::isSecured(int x, int y, int z){
	if(this->isInMap(x,y)){
		return this->mapElement[z][x][y]->isSecured();
	}
}

int Map::getElementType(int x, int y, int z){
	if (this->isInMap(x, y)) {
		if(this->mapElement[z][x][y] != NULL){
			return this->mapElement[z][x][y]->getElementType();
		}
	}
	else{
		return -1;
	}
}


/*
 * SAVE & RESTORE FUNCTIONS
 */

void Map::dumpMapToFile(){
	printf("Dumping map..");
	char* map = new char[Map::heigth*Map::width];
		for (int i = 0; this->isInMap(0, i); i++) {
			for (int u = 0; this->isInMap(u, i); u++) {
				sprintf(map, "%s%d",map,this->mapElement[0][u][i]->getElementType());
			}
			sprintf(map, "%s\n",map);
		}
	std::ofstream myFile;
	myFile.open(MAP_DUMP_FILE);
	if(myFile){
		myFile << map;
		myFile.close();
		printf("\tDumped\n");
	}
	else {
		printf("\tDump failed\n");
	}
	
}

void Map::loadFromFile(){
	printf("Loading map..\n");
	std::ifstream myFile;
	std::string fileContent;
	myFile.open(MAP_DUMP_FILE);
	if(myFile){
		std::string line; // variable contenant chaque ligne lue
		
		/*empty,
		ground,			// pense-bete
		water,
		tower,
		wall*/
				
						
		for ( int i = 0; std::getline(myFile, line ) && i < Map::heigth;i++){
			for (int u = 0; u < line.length() && u < Map::width; u++) {
				
				char currentCase = line[u];
				switch (atoi(&currentCase)) {
						
					case MapElement::water:
						this->mapElement[0][u][i] = new Water();
						this->waterList->addMapElement(this->mapElement[0][u][i]);
						this->mapElement[1][u][i] = new MapElement();
						break;
					case MapElement::wall:
						
						this->mapElement[1][u][i] = new Wall();
						this->wallList->addMapElement(this->mapElement[1][u][i]);
						this->mapElement[0][u][i] = new Ground();
						break;

					case MapElement::tower:
						this->mapElement[1][u][i] = new Tower();
						this->mapElement[0][u][i] = new MapElement();
						this->towerList->addMapElement(this->mapElement[1][u][i]);
						break;

					case MapElement::canon:
						this->mapElement[0][u][i] = new Ground();
						this->mapElement[0][u][i]->setSecured();
						this->securedList->addMapElement(this->mapElement[0][u][i]);
						this->mapElement[1][u][i] = new MapElement();
						this->buildCanon(u,i);
						break;
				
					default:
						this->mapElement[0][u][i] = new Ground();
						this->mapElement[1][u][i] = new MapElement();
						break;
					
				}
				this->mapElement[0][u][i]->SetPosition(u*MapElement::getWidth(), i*MapElement::getHeigth());
				this->mapElement[1][u][i]->SetPosition(u*MapElement::getWidth(), i*MapElement::getHeigth());
				this->mapElement[2][u][i] = new MapElement();				
				this->mapElement[2][u][i]->SetPosition(u*MapElement::getWidth(), i*MapElement::getHeigth());
			}
		}

		myFile.close();
	}
}

void Map::Render(sf::RenderTarget& Target) const {

		for (int i = 0; i<Map::layers; i++) {
			for (int u = 0; u < Map::width/MapElement::getWidth(); u++) {
				for (int v = 0; v < Map::heigth/MapElement::getHeigth(); v++) {
					if (this->mapElement[i][u][v] != NULL && this->mapElement[i][u][v]->isVisible()) {
						Target.Draw(*this->mapElement[i][u][v]);
					}
				}
			}
			
			for (int i = 0; i<this->canonList->size(); i++) {
				Canon* myCanon =  this->canonList->getObjectAt(i);
				int u = myCanon->getNumberOfBullets();
				for (int v = 0; v < u; v++) {
					try{
						Bullet* myBullet = myCanon->getBulletAt(v);
						Target.Draw(*myBullet);
					}
					catch (std::exception) {
						// On risque de se prendre des exception, lors de la 
						// manipulation des vectors. Ce n'est pas important pour Render().
					}
				}
			}
			
			for (int i = 0; i<this->boatList->size(); i++) {
				Boat* myBoat =  this->boatList->getObjectAt(i);
				
				int u = myBoat->getNumberOfBullets();
				for (int v = 0; v < u; v++) {
					try{
					Bullet* myBullet = myBoat->getBulletAt(v);
					Target.Draw(*myBullet);
					}
					catch (std::exception) {
					// Pareil que plus haut.
					}
				}
			}
			
	}
}


void Map::triggerFire(){
	for(int i = 0; i<this->canonList->size();i++){
		Canon* myCanon =  this->canonList->getObjectAt(i);
		myCanon->fire(EventManager::getInstance()->getMouseMapRelativePosition());
	}
}

void Map::triggerBoatFire(){
	for(int i = 0; i<this->boatList->size();i++){
		if (sf::Randomizer::Random(0,100) > 25 && this->wallList->size() > 0) {
			Boat* myBoat =  this->boatList->getObjectAt(i);
			myBoat->fire(this->wallList->getObjectPositionAt(sf::Randomizer::Random(0,this->wallList->size()-1)));
		}
	}
}

void Map::checkZone(int x, int y){
	Index<MapElement*>* encircledElements = new Index<MapElement*>;
	int position[8][2] = 
	{
		{x-1, y-1},
		{x-1, y  },
		{x-1, y+1},
		{x  , y+1},
		{x+1, y+1},
		{x+1, y  },
		{x+1, y-1},
		{x  , y-1}
		
	};
    
    
    for (int i = 0; i < 8; i++) {
        if (this->isInMap(position[i][0],position[i][1]) && this->mapElement[1][position[i][0]][position[i][1]]->getElementType() != MapElement::wall && ! this->mapElement[0][position[i][0]][position[i][1]]->isSecured() && mapElement[0][position[i][0]][position[i][1]]->getElementType() != MapElement::water && ! this->mapElement[0][position[i][0]][position[i][1]]->isChecked()){
            this->isClosed = true;
            try {
				this->mapElement[0][position[i][0]][position[i][1]]->setChecked();
				encircledElements->addObject(this->mapElement[0][position[i][0]][position[i][1]]);
				encircledElements->addObject(this->mapElement[1][position[i][0]][position[i][1]]);

                propagation(position[i][0], position[i][1],encircledElements);
            }
            catch (EdgeException) {
            }
            if (this->isClosed) {
                validateZone(position[i][0], position[i][1],encircledElements);
				encircledElements->clear();

            }
			else{
				for (int i = 0; i < encircledElements->size(); i++) {
					encircledElements->getObjectAt(i)->unsetChecked();
				}
				encircledElements->clear();
			}
		}
    }
}
void Map::propagation(int x, int y,Index<MapElement*>* encircledElements){
			
	int position[8][2] = 
	{
		{x-1, y-1},
		{x-1, y  },
		{x-1, y+1},
		{x  , y+1},
		{x+1, y+1},
		{x+1, y  },
		{x+1, y-1},
		{x  , y-1}
		
	};
	
    for (int i = 0; i < 8; i++) {
		
		if (this->isInMap(position[i][0],position[i][1]) && mapElement[0][position[i][0]][position[i][1]]->getElementType() != MapElement::water && ! mapElement[0][position[i][0]][position[i][1]]->isChecked()){
			if (mapElement[1][position[i][0]][position[i][1]]->getElementType() != MapElement::wall) {
				this->mapElement[0][position[i][0]][position[i][1]]->setChecked();
				encircledElements->addObject(this->mapElement[0][position[i][0]][position[i][1]]);
				encircledElements->addObject(this->mapElement[1][position[i][0]][position[i][1]]);
				propagation(position[i][0],position[i][1],encircledElements);
				
			}
			
					}
		
		else if (this->isInMap(position[i][0],position[i][1]) && (position[i][0] == 0 || position[i][1] == 0 || position[i][0] == Map::width || position[i][1] == Map::heigth || this->mapElement[0][position[i][0]][position[i][1]]->getElementType() == MapElement::water))  {
			this->isClosed = false;
			throw EdgeException();
		}
    }
}


void Map::validateZone(int x, int y,Index<MapElement*>* encircledElements){
	
	for (int i = 0; i < encircledElements->size(); i++) {
		encircledElements->getObjectAt(i)->setSecured();
		encircledElements->getObjectAt(i)->unsetChecked();
		this->securedList->addObject(encircledElements->getObjectAt(i));
	}
	encircledElements->clear();
	
}

void Map::buildBoat(int x, int y){
	sf::Lock Lock(globalLock);
	if (isInMap(x, y) && this->mapElement[0][x][y]->getElementType() == MapElement::water && this->mapElement[1][x][y]->getElementType() == MapElement::empty ) {
		delete this->mapElement[1][x][y];
		Boat* myBoat = new Boat();
		this->mapElement[1][x][y] = myBoat;
		this->mapElement[1][x][y]->SetPosition(x*MapElement::getWidth(), y*MapElement::getHeigth());
		this->boatList->addObject(myBoat);
		
	}
	
}

void Map::buildBoat(){
	sf::Vector2<int> casePos = this->waterList->getObjectMapPositionAt(sf::Randomizer::Random(0,this->waterList->size()-1));
	this->buildBoat(casePos.x,casePos.y);
}

void Map::destroyAllBoats(){
	for (int i = this->boatList->size()-1; i >= 0; i--) {
		this->destroyBoat(this->boatList->getObjectMapPositionAt(i).x, this->boatList->getObjectMapPositionAt(i).y);
	}
}


void Map::destroyBoat(int x, int y){
	if (isInMap(x, y)) {
		if(this->mapElement[1][x][y]->getElementType() == MapElement::boat || this->mapElement[1][x][y]->getElementType() == MapElement::killedBoat ) {
			globalLock.Lock();
			int boatId = this->mapElement[1][x][y]->getId();
			this->boatList->deleteObjectAt(boatId);
			this->mapElement[1][x][y] = new MapElement;
			this->mapElement[1][x][y]->SetPosition(x*MapElement::getWidth(), y*MapElement::getHeigth());
			globalLock.Unlock();
		 
		}
	}
}

int Map::getSecuredTowerNumber(){
	int securedTowers = 0;
	for (int i = 0; i < this->towerList->size(); i++) {
		if (this->towerList->getObjectAt(i)->isSecured()) {
			securedTowers++;
		}
	}
	return securedTowers;
}

void Map::manageWeapons(){
	globalLock.Lock();
	for(int i = 0; i<this->canonList->size();i++){
		WeaponObject* myWeapon =  this->canonList->getObjectAt(i);
		for(int u = 0; u < myWeapon->getNumberOfBullets(); u++){
			
			int weaponX = myWeapon->getBulletAt(u)->getMapPosition().x;
			int weaponY = myWeapon->getBulletAt(u)->getMapPosition().y;
			if (this->isInMap(weaponX,weaponY) && this->mapElement[1][weaponX][weaponY]->getElementType() == MapElement::boat ) {
				WeaponObject* myBoat = (WeaponObject*) this->mapElement[1][weaponX][weaponY];
				myBoat->disable();
				myWeapon->deleteBulletAt(u);
			}
			else if(!this->isInMap(myWeapon->getBulletAt(u)->GetPosition())){
				myWeapon->deleteBulletAt(u);
			}
		}
	}
	
	
	for(int i = 0; i<this->boatList->size();i++){
		WeaponObject* myWeapon =  this->boatList->getObjectAt(i);
		for(int u = 0; u < myWeapon->getNumberOfBullets(); u++){
			
			
			int weaponX = myWeapon->getBulletAt(u)->getMapPosition().x;
			int weaponY = myWeapon->getBulletAt(u)->getMapPosition().y;
			if (this->isInMap(weaponX,weaponY) && this->mapElement[1][weaponX][weaponY]->getElementType() == MapElement::wall ) {
				this->destroyWall(weaponX, weaponY);
				myWeapon->deleteBulletAt(u);
			}
			else if(!this->isInMap(myWeapon->getBulletAt(u)->GetPosition())){
				myWeapon->deleteBulletAt(u);
				
			}
		}
	}
	
	globalLock.Unlock();
}

void Map::updateZones(){
	globalLock.Lock();
	for (int i = 0; i < this->securedList->size(); i++) {
		this->securedList->getObjectAt(i)->unsetSecured();
	}
	this->securedList->clear();
	
	for (int i = 0; i < this->wallList->size(); i++) {
		this->checkZone(this->wallList->getObjectMapPositionAt(i).x,this->wallList->getObjectMapPositionAt(i).y);
	}
	globalLock.Unlock();
}
